/*
 * Copyright (c) 2011 Seoul National University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Jung Ho Ahn  ( gajh@snu.ac.kr )
 *
 */

#include "BlockAllocator.h"
#include "KmerHashTable.h"

pthread_mutex_t block_m;
BlockAllocatorMT bamt;
uint64_t BlockAllocatorMT::gCount     = 0;
uint64_t BlockAllocatorMT::block_size = 1024*1024*1024;

uint64_t BlockAllocatorST::allocate(uint64_t size)
{
  if (upperBound <= currPosition + size)
  {
    uint64_t alloc_size = (size > block_size) ? size : block_size;
    // not enough block space -- allocate more
    void * newSpace = malloc(alloc_size);
    allocated_blocks.push_back((uint64_t)newSpace);
    upperBound   = ((uint64_t)newSpace) + alloc_size;
    currPosition = (uint64_t)newSpace;
  }

  uint64_t p = currPosition;
  currPosition += size;
  return p;
}


void BlockAllocatorST::clearBlocks()
{
  for (list<uint64_t>::iterator iter = allocated_blocks.begin(); iter != allocated_blocks.end(); ++iter)
  {
    free((void *)(*iter));
  }
  allocated_blocks.clear();
}


BlockAllocatorMT::BlockAllocatorMT()
{
  if (gCount++ == 0)
  {
    pthread_mutex_init(&block_m, NULL);
  }
}


void BlockAllocatorMT::initiate()
{
  pthread_mutex_lock(&block_m);
  pthread_t currThread = pthread_self();
  map< pthread_t, pair<uint64_t, uint64_t> >::iterator iter = curr_blocks.find(currThread);

  if (iter == curr_blocks.end())
  {
    curr_blocks.insert(pair< pthread_t, pair<uint64_t, uint64_t> >(currThread, pair<uint64_t, uint64_t>(0, 0)));
  }
  pthread_mutex_unlock(&block_m);
}


uint64_t BlockAllocatorMT::allocate(uint64_t size)
{
  pthread_t currThread = pthread_self();
  map< pthread_t, pair<uint64_t, uint64_t> >::iterator iter = curr_blocks.find(currThread);

  /*if (iter == curr_blocks.end())
  {
    curr_blocks.insert(pair< pthread_t, pair<uint64_t, uint64_t> >(currThread, pair<uint64_t, uint64_t>(NULL, NULL)));
    iter = curr_blocks.find(currThread);
  }*/
  uint64_t upperBound   = iter->second.first;
  uint64_t currPosition = iter->second.second;

  if (upperBound <= currPosition + size)
  {
    // not enough block space -- allocate more
    pthread_mutex_lock(&block_m);
    uint64_t alloc_size = (size > block_size) ? size : block_size;
    void * newSpace = malloc(alloc_size);
    allocated_blocks.push_back((uint64_t)newSpace);
    iter->second.first = ((uint64_t)newSpace) + alloc_size;
    currPosition = (uint64_t)newSpace;
    pthread_mutex_unlock(&block_m);
  }

  iter->second.second = currPosition + size;
  return currPosition;
}


void BlockAllocatorMT::clearBlocks(bool remove_thread_info)
{
  for (list<uint64_t>::iterator iter = allocated_blocks.begin(); iter != allocated_blocks.end(); ++iter)
  {
    free((void *)(*iter));
  }
  allocated_blocks.clear();

  if (remove_thread_info == true)
  {
    curr_blocks.clear();
  }
  else
  {
    for (map< pthread_t, pair<uint64_t, uint64_t> >::iterator iter = curr_blocks.begin();
         iter != curr_blocks.end();
         ++iter)
    {
      iter->second.first  = 0;
      iter->second.second = 0;
    }
  }
}

